package com.game.utils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;

/**
 * 数学计算相关的工具类库.
 */
public class MathUtils {
    /** 一 */
    public static final double ONE = 1.0;
    /** 百 */
    public static final double HUNDRED = 100.0;
    /** 千 */
    public static final double THOUSAND = 1000.0;
    /** 万 */
    public static final double TEN_THOUSAND = 1_0000.0;
    /** 百万 */
    public static final double MILLION = 100_0000.0;
    /** 圆周率 */
    public static final float PI = 3.141592653589793238462643383279f;
    /** 欧拉数（Euler number），自然对数的底数， 当n->∞时，(1+1/n)^n的极限，∑1/n!的极限。 */
    public static final float E = 2.718281828459045235360287471352f;
    /** 黄金分割率，Golden Section，((根号5)-1)/2 */
    public static final float GS = 1.618033988749894848204586834365f;
    /** 半倍圆周率 */
    public static final float HALF_PI = 0.5f * PI;
    /** 双倍圆周率 */
    public static final float TWO_PI = 2.0f * PI;
    /** 圆周率倒数 */
    public static final float INV_PI = 1.0f / PI;
    /** 双倍圆周率倒数 */
    public static final float INV_TWO_PI = 1.0f / TWO_PI;
    /** 角度对弧度的兑换率 */
    public static final float DEG_TO_RAD = PI / 180f;
    /** 弧度对角度的兑换率 */
    public static final float RAD_TO_DEG = 180f / PI;
    /** 浮点标准误差 */
    public static final float STANDARD_ERROR = 0.00001f;
    /** 整型圆周率，放大一亿倍 */
    public static final int PI_INT = 314159265;
    /** 半倍整型圆周率，放大一亿倍 */
    public static final int HALF_PI_INT = 157079633;
    /** 双倍整型圆周率，放大一亿倍 */
    public static final int TWO_PI_INT = 628318531;

    /**
     * 计算两个参数的和，如果相加出现溢出那就返回{@code int}的最大值.
     * <p>
     * 区别于JDK的方法，仅仅认同判定方案，游戏世界，溢出时那就修正一个合理的值，一般调用此方法的游戏逻辑决不能因异常而中断
     *
     * @param x 第一个参数
     * @param y 第二个参数
     * @return 两个参数的和
     * @see Math#addExact(int, int)
     */
    public static int addExact(int x, int y) {
        try {
            return Math.addExact(x, y);
        } catch (ArithmeticException e) {
            return Integer.MAX_VALUE;
        }
    }

    /**
     * 计算两个参数的和，如果相加出现溢出那就返回{@code long}的最大值.
     * <p>
     * 区别于JDK的方法，仅仅认同判定方案，游戏世界，溢出时那就修正一个合理的值，一般调用此方法的游戏逻辑决不能因异常而中断
     *
     * @param x 第一个参数
     * @param y 第二个参数
     * @return 两个参数的和
     * @see Math#addExact(long, long)
     */
    public static long addExact(long x, long y) {
        try {
            return Math.addExact(x, y);
        } catch (ArithmeticException e) {
            return Long.MAX_VALUE;
        }
    }

    /**
     * 计算两个参数的乘积，如果相乘出现溢出那就返回{@code int}的最大值.
     * <p>
     * 区别于JDK的方法，仅仅认同判定方案，游戏世界，溢出时那就修正一个合理的值，一般调用此方法的游戏逻辑决不能因异常而中断
     *
     * @param x 第一个参数
     * @param y 第二个参数
     * @return 两个参数的乘积
     * @see Math#multiplyExact(int, int)
     */
    public static int multiplyExact(int x, int y) {
        try {
            return Math.multiplyExact(x, y);
        } catch (ArithmeticException e) {
            return Integer.MAX_VALUE;
        }
    }

    /**
     * 计算两个参数的乘积，如果相乘出现溢出那就返回{@code long}的最大值.
     * <p>
     * 区别于JDK的方法，仅仅认同判定方案，游戏世界，溢出时那就修正一个合理的值，一般调用此方法的游戏逻辑决不能因异常而中断
     *
     * @param x 第一个参数
     * @param y 第二个参数
     * @return 两个参数的乘积
     * @see Math#multiplyExact(long, long)
     */
    public static long multiplyExact(long x, long y) {
        try {
            return Math.multiplyExact(x, y);
        } catch (ArithmeticException e) {
            return Long.MAX_VALUE;
        }
    }

    /**
     * 计算两点(x1,y1)到(x2,y2)的距离.
     * <p>
     * Math.sqrt(|x1-x2|² + |y1-y2|²)
     *
     * @param x1 坐标X1
     * @param y1 坐标Y1
     * @param x2 坐标X2
     * @param y2 坐标Y2
     * @return 两点的距离
     */
    public static double distance(int x1, int y1, int x2, int y2) {
        final double x = Math.abs(x1 - x2);
        final double y = Math.abs(y1 - y2);
        return Math.sqrt(x * x + y * y);
    }

    /**
     * 计算两点(x1,y1)到(x2,y2)的距离.
     * <p>
     * Math.sqrt(|x1-x2|² + |y1-y2|²)
     *
     * @param x1 坐标X1
     * @param y1 坐标Y1
     * @param x2 坐标X2
     * @param y2 坐标Y2
     * @return 两点的距离
     */
    public static double distance(double x1, double y1, double x2, double y2) {
        final double x = Math.abs(x1 - x2);
        final double y = Math.abs(y1 - y2);
        return Math.sqrt(x * x + y * y);
    }

    /**
     * 判定两点(x1,y1)和(x2,y2)是否相邻.
     * <p>
     * 可用于两个AOI是否相邻判定
     *
     * @param x1 坐标X1
     * @param y1 坐标Y1
     * @param x2 坐标X2
     * @param y2 坐标Y2
     * @return 如果两坐标相邻返回true, 否则返回false
     */
    public static boolean adjacent(int x1, int y1, int x2, int y2) {
        return Math.abs(x1 - x2) <= 1 && Math.abs(y1 - y2) <= 1;
    }

    /**
     * 向下取整，并返回int值.
     *
     * @param a 一个带有小数的数值
     * @return 返回向下取整后的int值
     */
    public static int floorInt(double a) {
        return (int) Math.floor(a);
    }

    /**
     * 向下取整，并返回long值.
     *
     * @param a 一个带有小数的数值
     * @return 返回向下取整后的long值
     */
    public static long floorLong(double a) {
        return (long) Math.floor(a);
    }

    /**
     * 向上取整，并返回int值.
     *
     * @param a 一个带有小数的数值
     * @return 返回向上取整后的int值
     */
    public static int ceilInt(double a) {
        return (int) Math.ceil(a);
    }

    /**
     * 向上取整，并返回long值.
     *
     * @param a 一个带有小数的数值
     * @return 返回向上取整后的long值
     */
    public static long ceilLong(double a) {
        return (long) Math.ceil(a);
    }

    /**
     * 4舍5入取整，并返回int值.
     *
     * @param a 一个带有小数的数值
     * @return 返回向上取整后的int值
     */
    public static int roundInt(double a) {
        return (int) Math.round(a);
    }

    /**
     * 4舍5入取整，并返回long值.
     *
     * @param a 一个带有小数的数值
     * @return 返回向上取整后的long值
     */
    public static long roundLong(double a) {
        return (long) Math.round(a);
    }

    /**
     * 格式化小数位数的方法.
     * <p>
     * 采用了{@link BigDecimal#setScale(int, RoundingMode)}方式来保留小数位数<br>
     * 默认舍入方式为4舍5入, 参考{@link RoundingMode#HALF_UP}
     *
     * @param value    原始值
     * @param newScale 保留小数位数
     * @return 返回要被保留指定小数位数的值.
     */
    public static float formatScale(float value, int newScale) {
        return formatScale(value, newScale, RoundingMode.HALF_UP);
    }

    /**
     * 格式化小数位数的方法.
     * <p>
     * 采用了{@link BigDecimal#setScale(int, RoundingMode)}方式来保留小数位数
     *
     * @param value    原始值
     * @param newScale 保留小数位数
     * @param mode     被保留位数后舍入方式，参考{@link RoundingMode}
     * @return 返回要被保留指定小数位数的值.
     */
    public static float formatScale(float value, int newScale, RoundingMode mode) {
        return BigDecimal.valueOf(value).setScale(newScale, mode).floatValue();
    }

    /**
     * 格式化小数位数的方法.
     * <p>
     * 采用了{@link BigDecimal#setScale(int, RoundingMode)}方式来保留小数位数<br>
     * 默认舍入方式为4舍5入, 参考{@link RoundingMode#HALF_UP}
     *
     * @param value    原始值
     * @param newScale 保留小数位数
     * @return 返回要被保留指定小数位数的值.
     */
    public static double formatScale(double value, int newScale) {
        return formatScale(value, newScale, RoundingMode.HALF_UP);
    }

    /**
     * 格式化小数位数的方法.
     * <p>
     * 采用了{@link BigDecimal#setScale(int, RoundingMode)}方式来保留小数位数
     *
     * @param value    原始值
     * @param newScale 保留小数位数
     * @param mode     被保留位数后舍入方式，参考{@link RoundingMode}
     * @return 返回要被保留指定小数位数的值.
     */
    public static double formatScale(double value, int newScale, RoundingMode mode) {
        return BigDecimal.valueOf(value).setScale(newScale, mode).doubleValue();
    }

    /**
     * N种资源掠夺最优计算方案.
     * <p>
     * 有N种资源，尝试抢其他的部分，但各种资源有一定的比例... <br>
     * 使用场景：SLG的城池掠夺资源计算
     *
     * @param <T>       资源类型
     * @param resources N种资源(参数选用LinkedHashMap，就是想按顺序优先扣前面的...)
     * @param max       掠夺的最大值
     * @param ratio     掠夺比例<b>建议：比例总和在100以内</b>
     * @return 一种最优的掠夺结果
     */
    public static <T> Map<T, Long> plunder(Map<T, Long> resources, long max, Map<T, Integer> ratio) {
        final Map<T, Long> result = new HashMap<>(resources.size());
        final int sum = ratio.values().stream().reduce(0, (a, b) -> a + b);
        final long step = max >= sum ? max / Math.min(1000, Math.max(10, ratio.values().stream().reduce(0, (a, b) -> a + b))) : max;
        // 总计要抢的资源量
        long total = max;

        while (total > 0) {
            // 标识是否还有资源可以抢...
            boolean flag = false;
            for (Map.Entry<T, Long> e : resources.entrySet()) {
                if (e.getValue() <= 0) {
                    continue;
                }

                // 只要有一种资源大于0都算还有资源
                flag = true;

                // 比例+小步长随便，让抢出来的资源效果更好些...
                long selfStep = step * ratio.getOrDefault(e.getKey(), 1) + RandomUtils.nextLong(step);
                long temp = selfStep > total ? total : selfStep;

                // 如果资源不足，就以当前有的抢光就好了...
                if (e.getValue() < temp) {
                    temp = e.getValue();
                }

                // 最终本次抢的值
                final long value = temp;
                e.setValue(e.getValue() - value);
                total -= temp;
                result.compute(e.getKey(), (k, v) -> v == null ? value : v.longValue() + value);

                // 抢满了就退出啦...
                if (total <= 0) {
                    break;
                }
            }

            // 没有资源可以抢时，就直接退出了...
            if (!flag) {
                break;
            }
        }
        return result;
    }

    /**
     * long类型的数值按比率转化为double类型的值.
     * <p>
     * 由于配置表在转化中精度丢失问题，建议策划配置的是long类型的数值，所以就有了这个转化方法。 <br>
     * 比如：约定XX列为百分比，那配置50，就是50%，等于0.5
     *
     * @param value long类型的数值
     * @param ratio 比率
     * @return double类型的值
     */
    public static double longToDouble(long value, double ratio) {
        return value / ratio;
    }

    /**
     * long类型的数值以千分比转化为double类型的值.
     * <p>
     * 参考 {@link MathUtils#longToDouble(long, double)}
     *
     * @param value long类型的数值
     * @return double类型的值
     */
    public static double permillage(long value) {
        return MathUtils.longToDouble(value, MathUtils.THOUSAND);
    }

    /**
     * long类型的数值以百分比转化为double类型的值.
     * <p>
     * 参考 {@link MathUtils#longToDouble(long, double)}
     *
     * @param value long类型的数值
     * @return double类型的值
     */
    public static double percentage(long value) {
        return MathUtils.longToDouble(value, MathUtils.HUNDRED);
    }

    /* 几何方法 */
    /** 判断是否包含指定的坐标 */
    public static boolean rectContain(float[] rect, float x, float y) {
        return (x >= rect[0] && y >= rect[1] && rect[2] > x && rect[3] > y);
    }

    /**
     * 线段相交判定， 必须确保b1>=a1,b2>=a2， 返回值：1为完全包含，2为相等，3为完全被包含，-1为完全不包含，0为相交。
     */
    public static int lineCross(float a1, float b1, float a2, float b2) {
        if (a2 > a1) {
            if (a2 >= b1)
                return -1;
            if (b2 <= b1)
                return 1;
            return 0;
        } else if (a2 < a1) {
            if (b2 <= a1)
                return -1;
            if (b2 >= b1)
                return 3;
            return 0;
        } else {
            if (b2 < b1)
                return 1;
            if (b2 == b1)
                return 2;
            return 3;
        }
    }

    /**
     * 矩形1和矩形2的相交判定， 必须确保x1<x2,y1<y2,x3<x4,y3<y4，
     * 返回值：1为完全包含，2为相等，3为完全被包含，-1为完全不包含，0为相交。
     */
    public static int rectCross(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) {
        int r1 = lineCross(x1, x2, x3, x4);
        int r2 = lineCross(y1, y2, y3, y4);
        if (r1 < 0 || r2 < 0)
            return -1;
        if (r1 == 1 && r2 == 1)
            return 1;
        if (r1 == 2 && r2 == 2)
            return 2;
        if (r1 == 3 && r2 == 3)
            return 3;
        return 0;
    }

    /** 矩形剪切方法 */
    public static void rectIntersection(float[] rect, float x1, float y1, float x2, float y2) {
        if (rect[0] < x1)
            rect[0] = x1;
        if (rect[2] > x2)
            rect[2] = x2;
        if (rect[1] < y1)
            rect[1] = y1;
        if (rect[3] > y2)
            rect[3] = y2;
    }

    /** 矩形联合方法 */
    public static void rectUnion(float[] rect, float x1, float y1, float x2, float y2) {
        if (rect[0] > x1)
            rect[0] = x1;
        if (rect[2] < x2)
            rect[2] = x2;
        if (rect[1] > y1)
            rect[1] = y1;
        if (rect[3] < y2)
            rect[3] = y2;
    }

    /**
     * 计算p点在p1和p2所连成的射线上的方向， 方向指以p1为原点，顺时针旋转或逆时针旋转能更快的扫到p点
     *
     * @param x
     *            ,y，表示指定p点
     * @param x1
     *            ,y1,x2,y2，表示p1p2两点连成的直线
     * @return -1表示顺时针，0表示在直线上，1表示逆时针
     */
    public static int getDirection(double x, double y, double x1, double y1, double x2, double y2) {
        // 平行于Y轴
        if (x1 == x2) {
            if (y1 == y2)
                throw new IllegalArgumentException(" getDirection, no line, " + x1 + ":" + y1);
            if (x > x1)
                return (y1 > y2) ? 1 : -1;
            if (x < x1)
                return (y2 > y1) ? 1 : -1;
            return 0;
        }
        // 原理：一条直线将平面分成上下2个部分（已经排除了平行于Y轴的情况），
        // y-y1>(y2-y1)*(x-x1)/(x2-x1)
        // 表示该点在直线的上方，对射线而言，就可以确定旋转方向了
        double t1 = (y - y1) * (x2 - x1);
        double t2 = (y2 - y1) * (x - x1);
        if (t1 > t2)
            return 1;
        if (t1 < t2)
            return -1;
        return 0;
    }

    /**
     * 计算p点在p1和p2所连成的线段上的垂线交点，
     *
     * @param p
     *            ，表示指定p点
     * @param x1
     *            ,y1,x2,y2，表示p1p2两点连成的线段
     * @return -1表示垂足在线段外，0表示垂足在端点上，1表示垂足在线段内
     */
    public static int letFallIntersection(double[] p, double x1, double y1, double x2, double y2) {
        double x = p[0], y = p[1];
        // 平行于Y轴
        if (x1 == x2) {
            if (y1 == y2)
                throw new IllegalArgumentException(" letFallIntersection, no line, " + x1 + ":" + y1);
            p[0] = x1;
            p[1] = y;
            if (y == y1 || y == y2)
                return 0;
            if (y2 > y1)
                return (y < y2 && y > y1) ? 1 : -1;
            return (y < y1 && y > y2) ? 1 : -1;
        }
        double k = (y2 - y1) / (x2 - x1);
        double kk = k * k;
        double xx = (kk * x1 + k * (y - y1) + x) / (kk + 1);
        double yy = k * (xx - x1) + y1;
        p[0] = xx;
        p[1] = yy;
        if (yy == y1 || yy == y2)
            return 0;
        if (y2 > y1)
            return (yy < y2 && yy > y1) ? 1 : -1;
        return (yy < y1 && yy > y2) ? 1 : -1;
    }

    /**
     * 求线段之间的交点，
     *
     * @param ax
     *            ,ay,bx,by，表示ab两点连成的线段
     * @param line
     *            ，表示指定线段
     * @param force
     *            ，表示如果虚线相交，是否强行求解交点
     * @return 返回-3表示平行无交点， 返回-2表示同线无交点， 返回-1表示虚线相交（直线交点在line参数中），
     *         返回0表示共线（交点的两端在line参数中）， 返回1表示相交（交点在line参数中）
     */
    public static int lineIntersect(double ax, double ay, double bx, double by, double[] line, boolean force) {
        double cx = line[0], cy = line[1], dx = line[2], dy = line[3];
        double x1, y1, x2, y2, x3, y3;
        if (!force) {
            // 进行矩形剪切判定
            if (cx > dx) {
                x1 = dx;
                x2 = ax;
            } else {
                x1 = cx;
                x2 = dx;
            }
            if (cy > dy) {
                y1 = cy;
                y2 = dy;
            } else {
                y1 = dy;
                y2 = cy;
            }
            double x4, y4;
            if (ax > bx) {
                x3 = bx;
                x4 = ax;
            } else {
                x3 = ax;
                x4 = bx;
            }
            if (ay > by) {
                y3 = ay;
                y4 = by;
            } else {
                y3 = by;
                y4 = ay;
            }
            if (x3 < x1)
                x3 = x1;
            if (x4 > x2)
                x4 = x2;
            if (x3 > x4)
                return -1;
            if (y3 > y1)
                y3 = y1;
            if (y4 < y2)
                y4 = y2;
            if (y3 > y4)
                return -1;
        }
        x1 = dx - cx;
        y1 = dy - cy;
        x2 = bx - ax;
        y2 = by - ay;
        // 线段跨立判定，需要判断是否相互跨立
        // 计算矢量叉积，判断c点和d点相对于b点的方向，
        // 如果b*c>0表示c在b的逆时针方向，b*c<0表示c在b的顺时针方向，b*c=0表示共线
        // x3=x1*(cy-ay)-(cx-ax)*y1;
        // y3=x1*(dy-ay)-(dx-ax)*y1;
        // if(x3>0&&y3>0) return -1;
        // if(x3<0&&y3<0) return -1;
        // x3=x2*(ay-cy)-(ax-cx)*y2;
        // y3=x2*(by-cy)-(bx-cx)*y2;
        // if(x3>0&&y3>0) return -1;
        // if(x3<0&&y3<0) return -1;

        // 分情况计算相交点
        // 平行于y轴
        if (x1 == 0) {
            if (x2 == 0) {
                if (cx != ax)
                    return -3;
                // 共线、同线
                line[0] = cx;
                line[2] = cx;
                if (cy < dy) {
                    y3 = cy;
                    cy = dy;
                    dy = y3;
                }
                if (ay < by) {
                    y3 = ay;
                    ay = by;
                    by = y3;
                }
                line[1] = (ay > cy) ? cy : ay;
                line[3] = (by > dy) ? by : dy;
                if (line[1] < line[3])
                    return -2;
                return 0;
            }
            line[0] = cx;
            line[1] = (cx - ax) * y2 / x2 + ay;
            // 判断交点是否在线段上
            if (cy < dy) {
                if (line[1] < cy || line[1] > dy)
                    return -1;
            } else {
                if (line[1] > cy || line[1] < dy)
                    return -1;
            }
            return 1;
        }
        if (x2 == 0) {
            line[0] = ax;
            line[1] = (ax - cx) * y1 / x1 + cy;
            // 判断交点是否在线段上
            if (ay < by) {
                if (line[1] < ay || line[1] > by)
                    return -1;
            } else {
                if (line[1] > ay || line[1] < by)
                    return -1;
            }
            return 1;
        }
        // 平行于x轴
        if (y1 == 0) {
            if (y2 == 0) {
                if (cy != ay)
                    return -3;
                // 共线、同线
                line[1] = cy;
                line[3] = cy;
                if (cx > dx) {
                    x3 = cx;
                    cx = dx;
                    dx = x3;
                }
                if (ax > bx) {
                    x3 = ax;
                    ax = bx;
                    bx = x3;
                }
                line[0] = (ax < cx) ? cx : ax;
                line[2] = (bx < dx) ? bx : dx;
                if (line[0] > line[2])
                    return -2;
                return 0;
            }
            line[0] = (cy - ay) * x2 / y2 + ax;
            line[1] = cy;
            // 判断交点是否在线段上
            if (cx < dx) {
                if (line[0] < cx || line[0] > dx)
                    return -1;
            } else {
                if (line[0] > cx || line[0] < dx)
                    return -1;
            }
            return 1;
        }
        if (y2 == 0) {
            line[0] = (ay - cy) * x1 / y1 + cx;
            line[1] = ay;
            // 判断交点是否在线段上
            if (ax < bx) {
                if (line[0] < ax || line[0] > bx)
                    return -1;
            } else {
                if (line[0] > ax || line[0] < bx)
                    return -1;
            }
            return 1;
        }
        // 斜率均存在且不为0的情况
        x3 = y1 / x1;
        y3 = y2 / x2;
        // 斜率相等
        if (x3 - y3 < STANDARD_ERROR && x3 - y3 > -STANDARD_ERROR) {
            // 平行
            x3 = x3 * (ax - cx) - ay + cy;
            if (x3 > STANDARD_ERROR || x3 < -STANDARD_ERROR)
                return -3;
            double x4, y4;
            // 共线、同线
            if (cx > dx) {
                x1 = dx;
                y1 = dy;
                x2 = cx;
                y2 = cy;
            } else {
                x1 = cx;
                y1 = cy;
                x2 = dx;
                y2 = dy;
            }
            if (ax > bx) {
                x3 = bx;
                y3 = by;
                x4 = ax;
                y4 = ay;
            } else {
                x3 = ax;
                y3 = ay;
                x4 = bx;
                y4 = by;
            }
            if (x3 < x1) {
                line[0] = x1;
                line[1] = y1;
            } else {
                line[0] = x3;
                line[1] = y3;
            }
            if (x4 < x2) {
                line[2] = x4;
                line[3] = y4;
            } else {
                line[2] = x2;
                line[3] = x2;
            }
            if (line[0] > line[2])
                return -2;
            return 0;
        }
        line[0] = (x3 * cx - y3 * ax + ay - cy) / (x3 - y3);
        line[1] = x3 * (line[0] - cx) + cy;
        // 判断交点是否在线段上
        if (cx < dx) {
            if (line[0] < cx || line[0] > dx)
                return -1;
        } else {
            if (line[0] > cx || line[0] < dx)
                return -1;
        }
        if (ax < bx) {
            if (line[0] < ax || line[0] > bx)
                return -1;
        } else {
            if (line[0] > ax || line[0] < bx)
                return -1;
        }
        return 1;
    }

}